package edu.towson.greenteam;

import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import java.util.LinkedList;
import java.util.ArrayList;

import edu.towson.greenteam.table.Table;
import edu.towson.greenteam.table.Tables;

/* CustomerQueue observes Table:
 * 1.  When a Table has all of its items delivered, it is considered "open" and
 *     customer from CustomerQueue will be dequeued and assigned to the table
 *     
 * CustomerQueue is observed by Orders:
 * 1.  When a customer is dequeued, per person in party will be created and 
 * 	   enqueued into Orders
 *     
 */
public class CustomerQueue extends Observable implements Observer, ClockTick {

    private ArrayList<Observer> observers;
    public Invoker i;
    private int MIN = 2;  //min number in party
    private int MAX = 8;  //max number in party
    private int ctcount = 0;   //clock tick count
    private LinkedList<Customer> customers;      //number in customer party
    private static double averageWaitingTime = 0;
    private double totalCustomersSeated = 0;

    public CustomerQueue(Invoker i) {
    	observers = new ArrayList<Observer>(); 
        customers = new LinkedList<Customer>();
        this.i = i;
    }

    @Override
    public void clockTick() {
        //After so many clock ticks add random number of people to end of queue
        ctcount++;
        Logger.GetInstance().LogMessage("Cust Q clocktick " + ctcount);
        if (ctcount == Consts.CUSTOMER_FLOW) {
        	//nextInt is exclusive to top range so nextInt(40) will give randoms btwn 0-39
            int rangeNum = (MAX - MIN)+1;   // 2-8
            Random rand = new Random();
            int pickedNumber = rand.nextInt(rangeNum) + MIN;
            customers.add(new Customer(pickedNumber, Simulation.currentClockTick));
            resetCountTickCount();
            // Check to see if there are any free tables so that the customer
            // can be seated immediately...
            assignTable(pickedNumber);

        }
    }
/*
    public void setRATE(int rate) {
        RATE = rate;
    }

    public int getRATE() {
        return RATE;
    }
*/
    public LinkedList<Customer> getCustomerQueue() {
        return customers;
    }

    public void resetCountTickCount() {
        ctcount = 0;
    }

    public void dequeue() {
        customers.removeFirst();
    }

    @Override
    public String toString() {
        if (customers.isEmpty()) {
            return "There are no customers waiting for a table at this time.";
        }
        String customerString = "";
        for (Customer c : customers) {
            customerString += "A party of " + c.getNumInParty() + " arrived at " + c.getArrivalTime() + "\n";
        }
        return customerString;
    }

    /************************************************************************
     * Methods for being OBSERVABLE (i.e. CustomerQueue observed by Orders)
     ***********************************************************************/ 
    @Override 
    public void addObserver(Observer o) { 
        this.observers.add(o); 
    }
    
    @Override
    public void notifyObservers() {
    }

    @Override
    public void notifyObservers(Object arg) {
    	for (int i=0; i<observers.size(); i++) {
    		Observer o = (Observer) observers.get(i);
    		o.update(this, arg);
    	}    	
    }

    /************************************************************************
     * Methods for being OBSERVER (i.e. CustomerQueue listens to Table)
     ***********************************************************************/
    @Override
    public void update(Observable arg0, Object arg) {
    	String changedClass = arg0.getClass().getCanonicalName();
    	if (changedClass == Consts.TABLES) {
	        Table tableJustServed = (Table) arg;
	        i.getTables().get(tableJustServed.getId()).setOccupied(false);
	        if (!customers.isEmpty()) {
	            //check if group can be seated
	            int numInGroup = customers.getFirst().getNumInParty();
	            assignTable(numInGroup);
	
	        }
    	}
    }
    
    
    public void assignTable(int numInGroup) {
    	
    	Tables tables = i.getTables();
    	Iterator<Table> NotOccupiedItr = tables.createTblNotOccupiedItr();
    	
    	//if there are not tables available custs cannot be seated
    	Table firstUnoccupiedTable=null;
    	if (NotOccupiedItr.hasNext()) 
    		firstUnoccupiedTable = NotOccupiedItr.next();

		if (firstUnoccupiedTable != null) {
    		
	    	// group is more than 4 less than 8 need a 2nd table
	        if (numInGroup > Consts.NUM_SEATS_PER_TABLE) {
	        	
		        	Table secondUnoccupiedTable = null;
		        	if (NotOccupiedItr.hasNext())
		        		secondUnoccupiedTable=NotOccupiedItr.next();
		
		        	if (secondUnoccupiedTable != null) {
		        		firstUnoccupiedTable.setOccupied(true);
			        	secondUnoccupiedTable.setOccupied(true);
			        	totalCustomersSeated++;
			        	Logger.GetInstance().LogMessage("OCCUPIED TABLE: " + firstUnoccupiedTable.getId() + " AND " + secondUnoccupiedTable.getId());          
		            	int timeSeated = Simulation.getCurrentClockTick();
                                int arrivalTime = customers.getFirst().getArrivalTime();
                                int waitingTime = timeSeated - arrivalTime;
                                averageWaitingTime = averageWaitingTime * (totalCustomersSeated - 1);
                                averageWaitingTime = (averageWaitingTime + waitingTime) / totalCustomersSeated;
                                dequeue();
		            	setChanged();                 
		            	Object[] params = {numInGroup, firstUnoccupiedTable, secondUnoccupiedTable};                 
		            	notifyObservers(params);

		        	}
	        } else { //only 1 table needed
                firstUnoccupiedTable.setOccupied(true);
                totalCustomersSeated++;
                Logger.GetInstance().LogMessage("OCCUPIED TABLE: " + firstUnoccupiedTable.getId());
                int timeSeated = Simulation.getCurrentClockTick();
                int arrivalTime = customers.getFirst().getArrivalTime();
                int waitingTime = timeSeated - arrivalTime;
                averageWaitingTime = averageWaitingTime * (totalCustomersSeated - 1);
                averageWaitingTime = (averageWaitingTime + waitingTime) / totalCustomersSeated;
                dequeue();
                setChanged();
                Object[] params = {numInGroup, firstUnoccupiedTable,null};
	        	notifyObservers(params);
	        }
    	}
    }

    public static double getAverageWaitingTime() {
        return averageWaitingTime;
    }
}
